/**
 * ConsoleCRS 是一个便于在控制台输出外部JS代码的插件
 * 全称 Console Cross Regino Scripting 控制台跨域脚本
 * author:MingZeY
 * email:1552904342@qq.com
 * 
 * 原理和XSS攻击有相似之处,CRS在console中执行JS代码，该JS代码可获取到document、window等原生浏览器变量
 * 本插件提供了基本的交互方法，各位少侠可自行发挥
 * 
 * 
 * 注意：
 * 1.使用CRS运行的代码将在每个tick最后执行，其代码中的作用域与调用的位置在并无联系，这意味着他们的变量以及作用域
 * 与原生的SCreeps代码不在同一个域中，而使用CRS运行的代码都在同一个域中，并且他们可以访问到window和document，请注意使用
 * 2.CRS运行的代码结果不会之间显示到Screeps的控制台中，你可以前往浏览器的开发者工具查看返回值或运行结果，
 * 或者你也可用写出一个div并将执行结果返回到该容器中直接在Screeps客户端查看
 * 3.写在代码文档中并使用CRS提交(push)的代码每个tick都会执行，如需只执行一次请自行创建识别状态的变量，例如判断全局变量是undefined
 * 4.运行的代码不耗费CPU，只在本地运行，只有本插件在运行时会耗费少量CPU
 * 
 * 警告：!!!请按标准的JS写法将代码写到所有的ConsoleCRS方法入参中，
 * 建议每行代码后添加“;”分号来结尾，否则在跨域执行的时候可能出现无法预料的问题
 */

global.ConsoleCRS = {

    /**
     * 跨域永久数据，该数据在内外部域一直存在
     * 他们的调用方式如下
     * 内部域：
     *  跨域数据对象：ConsoleCRS.data
     * 
     * 外部域：
     *  跨域数据对象：CRSCore.data
     * 
     * 注：数据只能由内部域向外部域传递，也就是说数据交互是单向的
     */
    data: {},
    /**
     * ConsoleCRS配置
     */
    Setting: {
        PrintRunTip: false,//是否开启命令执行提示，开启后，执行命令将在console输出

        //CRSCore Support
        CRSCore_Support_ScreepsDOM: true,//ScreepsHtmlDom树，快速获取Dom元素

        //VUE Support
        CRSCore_Support_VUE: true,//VUE支持，开启后，可以在代码中使用VUE组件

        //ElementUI Support
        CRSCore_Support_ElementUI: true,//ElementUI支持，开启后可以插入ElementUI组件
    },

    /**
     * 队列预设，每次pushCode的数据会通过该对象序列化后拉出去执行
     */
    RunQueue: {
        funlist: [],//放置待执行函数的数组
        data:{}
    },

    /**
     * 添加到运行队列
     * 使用pushCode向ConsoleCRS添加runCode运行队列
     * 通过pushCode添加的代码会按照顺序依次执行
     * 
     * 注意：需要在最后一次push代码后添加ConsoleCRS.pushFinish()
     */
    pushCode: function (fun) {
        global.ConsoleCRS.RunQueue.funlist.push(fun);
    },

    /**
     * 用于执行pushCode后的运行队列，和pushCode配套使用
     * 注意：pushFinish允许跨tick触发，但有可能导致某个pushCode的代
     * 码在n个tick内被push n 次，相应的这个方法也会执行多次，编写时请注意跨Tick逻辑
     */
    pushFinish: function () {

        //将数据拉倒RunQueue
        global.ConsoleCRS.RunQueue.data = global.ConsoleCRS.data;

        //生成执行对象的序列化字符串
        let serializeStr = ConsoleCRS.serialize(ConsoleCRS.RunQueue, "ConsoleCRSRunQueue");
        serializeStr = serializeStr.replace(/[\r\n]/g, "\\n");
        serializeStr = serializeStr.replace(/\"/g, "\\\"");
        serializeStr = serializeStr.replace(/\'/g, "\\\'");
        serializeStr = serializeStr.replace(/\\\\\"/g, "\\\\\\\"");//把 \\" 替换成 \\\" 


        //通过console抛出执行
        console.log((ConsoleCRS.Setting.PrintRunTip) ? ("[ConsoleCRS]命令已经在" + Game.time + "发送至外部域") : "" +
            "<script class='ConsoleHtmlRunCode'>" +
            "let ConsoleCRSRunQueue = new Object();" +
            "eval(" + "\"" + serializeStr + "\"" + ");" +
            "if(window.CRSCore){window.CRSCore.data = ConsoleCRSRunQueue.data;" +
            "for(fun of ConsoleCRSRunQueue.funlist){fun()}}else{console.warn(\"[ConsoleCRS/CRSCore]请在内部域使用ConsoleCRS.reMount()否则代码将无法执行\")};" +
            "let result = document.getElementsByClassName('ConsoleHtmlRunCode');" +
            "for(el of result){el.parentNode.parentNode.style.display=\"none\";el.remove()};" +
            "</script>");

        //清空队列
        global.ConsoleCRS.RunQueue.funlist = [];
        //清空数据
        global.ConsoleCRS.RunQueue.data = {};
    },

    /**
     * 挂在CRSCore内核，根据Setting配置相应的对象
     */
    reMount: function () {

        //激活外部域内部域执行通道
        console.log(
            "<script class='ConsoleHtmlRunCode'>" +
            "window.CRSCore = {}" +
            "</script>"
        );

        ConsoleCRS.pushCode(function () {
            //直接写入对象
            window.CRSCore = {
                version: "1.0",
                data: {},//预留交互数据位置
                appendEle: {},//添加的元素，例如插入外部JS，外部CSS，统一放在这里
            }

            //修改Console的标题样式
            $(".console-tab a")[0].innerHTML = "ConsoleCRS";
            $(".console-tab a")[0].style.color = "#FFC107";
        });

        //写入Setting中开启的对象

        //ScreepsDOM
        if (ConsoleCRS.Setting.CRSCore_Support_ScreepsDOM) {
            ConsoleCRS.pushCode(function () {
                window.CRSCore.ScreepsDom = {
                    TopContent: {
                        self: $(".navbar")[0],
                        Profile: {
                            self: $(".navbar-profile .ui-drop-group")[0],
                        }
                    },
                    LeftControls: {
                        self: $(".left-controls")[0],
                    },
                    ZoomControls: {
                        self: $(".zoom-controls")[0],
                    },
                    AsideContent: {
                        self: $(".aside-content")[0],
                        BottomGroup: $(".aside-block"),
                    },
                    EditorPlane: {
                        self: $(".editor-panel")[0],
                        NavTab: $(".editor-panel .nav.nav-tabs")[0],
                        ConsoleNav: $(".console-tab:first")[0],
                        ConsoleNavTitle: $(".console-tab a")[0],

                    },
                    RoomVisual: {
                        Window: $(".pixijs-renderer__container")[0],
                        Canvas: $(".room-visual")[0],
                        Stage: $(".pixijs-renderer__stage")[0]
                    }

                }
            });
        }

        //VUE
        if (ConsoleCRS.Setting.CRSCore_Support_VUE) {
            ConsoleCRS.pushCode(function () {
                //判断VUE是否有效
                if (typeof window.CRSCore.appendEle.VueScript == "undefined") {
                    //引入js
                    let head = document.getElementsByTagName('head')[0];
                    window.CRSCore.appendEle.VueScript = document.createElement('script');
                    let script = window.CRSCore.appendEle.VueScript;
                    script.src = 'https://unpkg.com/vue/dist/vue.js';
                    script.type = 'text/javascript';
                    head.appendChild(script);
                    console.warn("[CRSCore]VUE支持已启用 StateCode:0");
                } else {
                    console.warn("[CRSCore]VUE支持已启用 StateCode:1");
                }

            });
        }

        //ElementUI
        if (ConsoleCRS.Setting.CRSCore_Support_ElementUI) {
            ConsoleCRS.pushCode(function () {
                if (typeof window.CRSCore.appendEle.ElementUIlink == "undefined") {
                    console.log(typeof window.CRSCore.appendEle.ElementUIlink);
                    //插入CSS
                    var head = document.getElementsByTagName('head')[0];

                    window.CRSCore.appendEle.ElementUIlink = document.createElement('link');
                    let link = window.CRSCore.appendEle.ElementUIlink;
                    link.href = "https://unpkg.com/element-ui/lib/theme-chalk/index.css";
                    link.rel = 'stylesheet';
                    link.type = 'text/css';
                    head.appendChild(link);

                    //插入js
                    window.CRSCore.appendEle.ElementUIScript = document.createElement('script');
                    let script = window.CRSCore.appendEle.ElementUIScript;
                    script.src = 'https://unpkg.com/element-ui/lib/index.js';
                    script.type = 'text/javascript';
                    head.appendChild(script);
                    console.warn("[CRSCore]ElementUI支持已启用 StateCode:0");
                } else {
                    console.warn("[CRSCore]ElementUI支持已启用 StateCode:1");
                }

            });
        }



        ConsoleCRS.pushCode(function () {
            setTimeout(function () {
                $(".console-controls .md-button:eq(1)").trigger('click');
            });
        });

        ConsoleCRS.pushFinish();

        return 0;
    },

    /**
     * 清空控制台输出
     * 该方法简述了如何通过CRS来控制Screeps客户端的界面按钮，如下
     * ".console-controls .md-button:eq(1)"就是清除控制台输出按钮的 选择器路径
     * 您应该遵循这个方法体，外层使用setTimeout，内层选中您想要的按钮元素，最后使用trigger('click')触发
     * 
     * 如果您想获取其他按钮的 选择器路径，您可以前往WEB版的Screeps并启用开发者模式，通过检查元素操作获得
     */
    cleanOutput: function () {
        ConsoleCRS.pushCode(function () {
            setTimeout(function () {
                $(".console-controls .md-button:eq(1)").trigger('click');
            });
        });
    },




    /**
     * 对象序列化算法，该算法可将对象和对象中的方法序列化，通过该代码在本地序列化再
     * 通过CRS跨域到浏览器中反序列化以此来执行脚本
     */
    serialize: function (obj, name) {
        var result = "";
        function serializeInternal(o, path) {
            for (p in o) {
                var value = o[p];
                if (typeof value != "object") {
                    if (typeof value == "string") {
                        result += "" + path + "[" + (isNaN(p) ? "\"" + p + "\"" : p) + "] = " + "\"" + value.replace(/\"/g, "\\\"") + "\"" + ";";
                    } else {
                        result += "" + path + "[" + (isNaN(p) ? "\"" + p + "\"" : p) + "] = " + value + ";";
                    }
                }
                else {
                    if (value instanceof Array) {
                        result += "" + path + "[" + (isNaN(p) ? "\"" + p + "\"" : p) + "]" + "=" + "new Array();";
                        serializeInternal(value, path + "[" + (isNaN(p) ? "\"" + p + "\"" : p) + "]");
                    } else {
                        result += "" + path + "[" + (isNaN(p) ? "\"" + p + "\"" : p) + "]" + "=" + "new Object();";
                        serializeInternal(value, path + "[" + (isNaN(p) ? "\"" + p + "\"" : p) + "]");
                    }
                }
            }
        }
        serializeInternal(obj, name);
        return result;
    }
};


//注入ConsoleCRS 内核 CRSCore
ConsoleCRS.reMount();